package portal

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"distributed/grades"
	"distributed/registry"
)

func RegisterHandlers() {
	http.Handle("/", http.RedirectHandler("/students", http.StatusPermanentRedirect))

	h := new(studentsHandler)
	http.Handle("/students", h)
	http.Handle("/students/", h)
}

type studentsHandler struct{}

func (sh studentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	pathSegments := strings.Split(r.URL.Path, "/")
	switch len(pathSegments) {
	case 2:
		sh.renderStudents(w, r)
	case 3:
		id, err := strconv.Atoi(pathSegments[2])
		if err != nil {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		sh.renderStudent(w, r, id)
	case 4:
		id, err := strconv.Atoi(pathSegments[2])
		if err != nil || strings.ToLower(pathSegments[3]) != "grades" {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		sh.renderGrades(w, r, id)
	default:
		w.WriteHeader(http.StatusNotFound)
	}
}

func (sh studentsHandler) renderStudents(w http.ResponseWriter, r *http.Request) {
	var err error
	defer func() {
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Printf("Error retrieving students: %v", err)
		}
	}()

	serviceURLs, err := registry.GetProviders(registry.GradingService)
	if err != nil {
		return
	}

	var resp *http.Response
	for _, serviceURL := range serviceURLs {
		resp, err = http.Get(serviceURL + "/students")
		if err == nil {
			break
		}
	}
	if err != nil {
		return
	}

	var s grades.Students
	err = json.NewDecoder(resp.Body).Decode(&s)
	if err != nil {
		return
	}

	rootTemplate.Lookup("students.html").Execute(w, s)
}

func (sh studentsHandler) renderStudent(w http.ResponseWriter, r *http.Request, id int) {
	var err error
	defer func() {
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Printf("Error retrieving student: %v", err)
		}
	}()

	serviceURLs, err := registry.GetProviders(registry.GradingService)
	if err != nil {
		return
	}

	var resp *http.Response
	for _, serviceURL := range serviceURLs {
		resp, err = http.Get(fmt.Sprintf("%s/student/%d", serviceURL, id))
		if err == nil {
			break
		}
	}
	if err != nil {
		return
	}

	var s grades.Student
	err = json.NewDecoder(resp.Body).Decode(&s)
	if err != nil {
		return
	}

	rootTemplate.Lookup("student.html").Execute(w, s)
}

func (sh studentsHandler) renderGrades(w http.ResponseWriter, r *http.Request, id int) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	defer func() {
		w.Header().Add("location", fmt.Sprintf("/students/%v", id))
		w.WriteHeader(http.StatusTemporaryRedirect)
	}()
	title := r.FormValue("Title")
	gradeType := r.FormValue("Type")
	score, err := strconv.ParseFloat(r.FormValue("Score"), 32)
	if err != nil {
		log.Printf("Failed to parse score: %v", err)
		return
	}

	g := grades.Grade{
		Title: title,
		Type:  grades.GradeType(gradeType),
		Score: float32(score),
	}
	data, err := json.Marshal(g)
	if err != nil {
		log.Printf("Failed to convert grade to json: %v, data: %v", err, g)
		return
	}

	serviceURLs, err := registry.GetProviders(registry.GradingService)
	if err != nil {
		log.Printf("Failed to retrieve instance of Grading Service: %v", err)
		return
	}
	var resp *http.Response
	for _, serviceURL := range serviceURLs {
		resp, err = http.Post(
			fmt.Sprintf("%s/students/%d/grades", serviceURL, id),
			"application/json", bytes.NewBuffer(data),
		)
		if err == nil {
			if resp.StatusCode != http.StatusCreated {
				log.Printf(
					"Failed to save grade to Grading Service. Status: %d",
					resp.StatusCode,
				)
				err = fmt.Errorf("Status: %d", resp.StatusCode)
				continue
			}
			break
		}
	}
	if err != nil {
		log.Printf("Failed to save grade to Grading Service: %v", err)
		return
	}
}
